home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Browsers, Managers & Extensions / Mozilla Weave 0.2.7 / latest-weave.xpi / modules / xmpp / authenticationLayer.js next >
Text File  |  2008-07-08  |  13KB  |  354 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Bookmarks Sync.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2008
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Jono DiCarlo <jdicarlo@mozilla.org>
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37. const EXPORTED_SYMBOLS = [ "PlainAuthenticator", "Md5DigestAuthenticator" ];
  38.  
  39. var Cc = Components.classes;
  40. var Ci = Components.interfaces;
  41. var Cu = Components.utils;
  42.  
  43. Cu.import("resource://weave/log4moz.js");
  44.  
  45. /* Two implementations of SASL authentication:
  46.    one using MD5-DIGEST, the other using PLAIN.
  47.  
  48.  
  49. Here's the interface that each implementation must obey:
  50.  
  51. {
  52.   initialize( clientName, clientRealm, clientPassword );
  53.  
  54.   generateResponse( rootElem );
  55.  
  56.   // returns text of error message
  57.   getError();
  58. }
  59.  
  60. */
  61.  
  62. function BaseAuthenticator() {
  63. }
  64. BaseAuthenticator.prototype = {
  65.  COMPLETION_CODE: "success!",
  66.  
  67.  initialize: function( userName, realm, password  ) {
  68.     this._name = userName;
  69.     this._realm = realm;
  70.     this._password = password;
  71.     this._stepNumber = 0;
  72.     this._errorMsg = "";
  73.  },
  74.  
  75.  getError: function () {
  76.     /* Returns text of most recent error message.
  77.        Client code should call this if generateResponse() returns false
  78.        to see what the problem was. */
  79.     return this._errorMsg;
  80.  },
  81.  
  82.  generateResponse: function( rootElem ) {
  83.     /* Subclasses must override this.  rootElem is a DOM node which is
  84.        the root element of the XML the server has sent to us as part
  85.        of the authentication protocol.  return value: the string that
  86.        should be sent back to the server in response.  'false' if
  87.        there's a failure, or COMPLETION_CODE if nothing else needs to
  88.        be sent because authentication is a success. */
  89.  
  90.     this._errorMsg = "generateResponse() should be overridden by subclass.";
  91.     return false;
  92.  },
  93.  
  94.  verifyProtocolSupport: function( rootElem, protocolName ) {
  95.     /* Parses the incoming stream from the server to check whether the
  96.        server supports the type of authentication we want to do
  97.        (specified in the protocolName argument).
  98.        Returns false if there is any problem.
  99.      */
  100.     if ( rootElem.nodeName != "stream:stream" ) {
  101.       this._errorMsg = "Expected stream:stream but got " + rootElem.nodeName;
  102.       return false;
  103.     }
  104.  
  105.     dump( "Got response from server...\n" );
  106.     dump( "ID is " + rootElem.getAttribute( "id" ) + "\n" );
  107.     // TODO: Do I need to do anything with this ID value???
  108.     dump( "From: " + rootElem.getAttribute( "from" ) + "\n" );
  109.     if (rootElem.childNodes.length == 0) {
  110.       // No child nodes is unexpected, but allowed by the protocol.
  111.       // this shouldn't be an error.
  112.       this._errorMsg = "Expected child nodes but got none.";
  113.       return false;
  114.     }
  115.  
  116.     var child = rootElem.childNodes[0];
  117.     if (child.nodeName == "stream:error" ) {
  118.       this._errorMsg = this.parseError( child );
  119.       return false;
  120.     }
  121.  
  122.     if ( child.nodeName != "stream:features" ) {
  123.       this._errorMsg = "Expected stream:features but got " + child.nodeName;
  124.       return false;
  125.     }
  126.  
  127.     var protocolSupported = false;
  128.     var mechanisms = child.getElementsByTagName( "mechanism" );
  129.     for ( var x = 0; x < mechanisms.length; x++ ) {
  130.       if ( mechanisms[x].firstChild.nodeValue == protocolName ) {
  131.           protocolSupported = true;
  132.       }
  133.     }
  134.  
  135.     if ( !protocolSupported ) {
  136.       this._errorMsg = protocolName + " not supported by server!";
  137.       return false;
  138.     }
  139.     return true;
  140.  }
  141.  
  142. };
  143.  
  144. function Md5DigestAuthenticator( ) {
  145.   /* SASL using DIGEST-MD5 authentication.
  146.      Uses complicated hash of password
  147.      with nonce and cnonce to obscure password while preventing replay
  148.      attacks.
  149.  
  150.      See http://www.faqs.org/rfcs/rfc2831.html
  151.      "Using Digest Authentication as a SASL mechanism"
  152.  
  153.      TODO: currently, this is getting rejected by my server.
  154.      What's wrong?
  155.   */
  156. }
  157. Md5DigestAuthenticator.prototype = {
  158.  
  159.   _makeCNonce: function( ) {
  160.     return "\"" + Math.floor( 10000000000 * Math.random() ) + "\"";
  161.   },
  162.  
  163.   generateResponse: function Md5__generateResponse( rootElem ) {
  164.     if ( this._stepNumber == 0 ) {
  165.  
  166.       if ( this.verifyProtocolSupport( rootElem, "DIGEST-MD5" ) == false ) {
  167.         return false;
  168.       }
  169.       // SASL step 1: request that we use DIGEST-MD5 authentication.
  170.       this._stepNumber = 1;
  171.       return "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>";
  172.  
  173.     } else if ( this._stepNumber == 1 ) {
  174.  
  175.       // proceed to SASL step 2: are you asking for a CHALLENGE?!?
  176.       var challenge = this._unpackChallenge( rootElem.firstChild.nodeValue );
  177.       dump( "Nonce is " + challenge.nonce + "\n" );
  178.       // eg:
  179.       // nonce="3816627940",qop="auth",charset=utf-8,algorithm=md5-sess
  180.  
  181.       // Now i have the nonce: make a digest-response out of
  182.       /* username: required
  183.        realm: only needed if realm is in challenge
  184.        nonce: required, just as recieved
  185.        cnonce: required, opaque quoted string, 64 bits entropy
  186.        nonce-count: optional
  187.        qop: (quality of protection) optional
  188.        serv-type: optional?
  189.        host: optional?
  190.        serv-name: optional?
  191.        digest-uri: "service/host/serv-name" (replaces those three?)
  192.        response: required (32 lowercase hex),
  193.        maxbuf: optional,
  194.        charset,
  195.        LHEX (32 hex digits = ??),
  196.        cipher: required if auth-conf is negotiatedd??
  197.        authzid: optional
  198.       */
  199.  
  200.  
  201.       // TODO: Are these acceptable values for realm, nonceCount, and
  202.       // digestUri??
  203.       var nonceCount = "00000001";
  204.       var digestUri = "xmpp/" + this.realm;
  205.       var cNonce = this._makeCNonce();
  206.     // Section 2.1.2.1 of RFC2831
  207.       var A1 = str_md5( this.name + ":" + this.realm + ":" + this.password ) + ":" + challenge.nonce + ":" + cNonce;
  208.       var A2 = "AUTHENTICATE:" + digestUri;
  209.       var myResponse = hex_md5( hex_md5( A1 ) + ":" + challenge.nonce + ":" + nonceCount + ":" + cNonce + ":auth" + hex_md5( A2 ) );
  210.  
  211.       var responseDict = {
  212.       username: "\"" + this.name + "\"",
  213.       nonce: challenge.nonce,
  214.       nc: nonceCount,
  215.       cnonce: cNonce,
  216.       qop: "\"auth\"",
  217.       algorithm: "md5-sess",
  218.       charset: "utf-8",
  219.       response: myResponse
  220.       };
  221.       responseDict[ "digest-uri" ] = "\"" + digestUri + "\"";
  222.       var responseStr = this._packChallengeResponse( responseDict );
  223.       this._stepNumber = 2;
  224.       return "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + responseStr + "</response>";
  225.  
  226.     } else if ( this._stepNumber = 2 ) {
  227.       dump( "Got to step 3!" );
  228.       // At this point the server might reject us with a
  229.       // <failure><not-authorized/></failure>
  230.       if ( rootElem.nodeName == "failure" ) {
  231.         this._errorMsg = rootElem.firstChild.nodeName;
  232.         return false;
  233.       }
  234.       //this._connectionStatus = this.REQUESTED_SASL_3;
  235.     }
  236.     this._errorMsg = "Can't happen.";
  237.     return false;
  238.   },
  239.  
  240.   _unpackChallenge: function( challengeString ) {
  241.     var challenge = atob( challengeString );
  242.     dump( "After b64 decoding: " + challenge + "\n" );
  243.     var challengeItemStrings = challenge.split( "," );
  244.     var challengeItems = {};
  245.     for ( var x in challengeItemStrings ) {
  246.       var stuff = challengeItemStrings[x].split( "=" );
  247.       challengeItems[ stuff[0] ] = stuff[1];
  248.     }
  249.     return challengeItems;
  250.   },
  251.  
  252.   _packChallengeResponse: function( responseDict ) {
  253.     var responseArray = []
  254.     for( var x in responseDict ) {
  255.       responseArray.push( x + "=" + responseDict[x] );
  256.     }
  257.     var responseString = responseArray.join( "," );
  258.     dump( "Here's my response string: \n" );
  259.     dump( responseString + "\n" );
  260.     return btoa( responseString );
  261.   }
  262. };
  263. Md5DigestAuthenticator.prototype.__proto__ = new BaseAuthenticator();
  264.  
  265.  
  266. function PlainAuthenticator( ) {
  267.   /* SASL using PLAIN authentication, which sends password in the clear. */
  268. }
  269. PlainAuthenticator.prototype = {
  270.  
  271.   generateResponse: function( rootElem ) {
  272.     if ( this._stepNumber == 0 ) {
  273.       if ( this.verifyProtocolSupport( rootElem, "PLAIN" ) == false ) {
  274.         return false;
  275.       }
  276.       var authString = btoa( this._realm + '\0' + this._name + '\0' + this._password );
  277.       this._stepNumber = 1;
  278.  
  279.       // XXX why does this not match the stanzas in XEP-025?
  280.       return "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" + authString + "</auth>";
  281.  
  282.     } else if ( this._stepNumber == 1 ) {
  283.       if ( rootElem.nodeName == "failure" ) {
  284.           // Authentication rejected: username or password may be wrong.
  285.           this._errorMsg = rootElem.firstChild.nodeName;
  286.           return false;
  287.       } else if ( rootElem.nodeName == "success" ) {
  288.         // Authentication accepted: now we start a new stream for
  289.         // resource binding.
  290.         /* RFC3920 part 7 says: upon receiving a success indication  within the
  291.            SASL negotiation, the client MUST send a new stream header to the
  292.            server, to which the serer MUST respond with a stream header
  293.            as well as a list of available stream features. */
  294.         // TODO: resource binding happens in any authentication mechanism
  295.         // so should be moved to base class.
  296.         this._stepNumber = 2;
  297.         return "<?xml version='1.0'?><stream:stream to='" +
  298.                this._realm +
  299.                "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>";
  300.       }
  301.     } else if ( this._stepNumber == 2 ) {
  302.       // See if the server is asking us to bind a resource, and if it's
  303.       // asking us to start a session:
  304.       var bindNodes = rootElem.getElementsByTagName( "bind" );
  305.       if ( bindNodes.length > 0 ) {
  306.           this._needBinding = true;
  307.       }
  308.  
  309.       var sessionNodes = rootElem.getElementsByTagName( "session" );
  310.       if ( sessionNodes.length > 0 ) {
  311.           this._needSession = true;
  312.       }
  313.  
  314.       if ( !this._needBinding && !this._needSession ) {
  315.           // Server hasn't requested either: we're done.
  316.           return this.COMPLETION_CODE;
  317.       }
  318.  
  319.       if ( this._needBinding ) {
  320.         // Do resource binding:
  321.         // Tell the server to generate the resource ID for us.
  322.         this._stepNumber = 3;
  323.         return "<iq type='set' id='bind_1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>";
  324.       }
  325.  
  326.       this._errorMsg = "Server requested session not binding: can't happen?";
  327.       return false;
  328.     } else if ( this._stepNumber == 3 ) {
  329.       // Pull the JID out of the stuff the server sends us.
  330.       var jidNodes = rootElem.getElementsByTagName( "jid" );
  331.       if ( jidNodes.length == 0 ) {
  332.           this._errorMsg = "Expected JID node from server, got none.";
  333.           return false;
  334.       }
  335.       this._jid = jidNodes[0].firstChild.nodeValue;
  336.       // TODO: Does the client need to do anything special with its new
  337.       // "client@host.com/resourceID"  full JID?
  338.  
  339.       // If we still need to do session, then we're not done yet:
  340.       if ( this._needSession ) {
  341.           this._stepNumber = 4;
  342.           return "<iq to='" + this._realm + "' type='set' id='sess_1'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>";
  343.       } else {
  344.           return this.COMPLETION_CODE;
  345.       }
  346.     } else if ( this._stepNumber == 4 ) {
  347.       // OK, now we're done.
  348.       return this.COMPLETION_CODE;
  349.     }
  350.   }
  351.  
  352. };
  353. PlainAuthenticator.prototype.__proto__ = new BaseAuthenticator();
  354.